﻿/*  This file is part of SvnCommitToFTP.
 *  
 *  SvnCommitToFTP is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 
 *  SvnCommitToFTP is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with SvnCommitToFTP.  If not, see <http://www.gnu.org/licenses/>.
 *  
 *  Copyright 2008 Alexandru Petrescu (alex.petrescu@gmail.com)
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using Softec.AprSharp;
using Softec.SubversionSharp;

namespace SvnCommitToFTP
{
    class SvnCommitToFTP
    {
        private string svnPath;
        private string svnRev;
        private TextWriter logger = null;
        private SvnClient client;
        private SvnRevision rev;
        private SvnPath path;

        private string tempFile = System.IO.Path.GetTempPath() + "tempSVNCommitFile";

        private IList<SvnLogChangePathEntry> logEntries = new List<SvnLogChangePathEntry>();

        public string ftpServerURL = "";
        public string ftpUserID = "";
        public string ftpPassword = "";
        public string ftpRemoteDir = "";

        private SvnLogChangePathEntry currentPath;

        public SvnCommitToFTP(string inPath, string inRev, TextWriter inLogger)
        {
            logger = inLogger;
            svnPath = inPath;
            svnRev = inRev;

            if (!SvnPath.PathIsUrl(svnPath))
                svnPath = "file://" + svnPath;
            
            client = new SvnClient();
            rev = new SvnRevision(int.Parse(svnRev));
            path = new SvnPath(svnPath, client.Pool);

        }

        public void execute()
        {
            GetChanges();

            if (logEntries.Count > 0)
            {
                SyncChanges();
            }
        }

        private void SyncChanges()
        {
            foreach(SvnLogChangePathEntry chPath in logEntries)
            {
                currentPath = chPath;
                switch(chPath.Action)
                {
                    case 'M':
                    case 'A':

                        client.Info(path.Value + chPath.Path, rev, rev, new SvnClientBase.InfoReceiver(CreateInfoReceiver), IntPtr.Zero, false);

                        break;
                    case 'D':
                        try
                        {
                            DeleteFileFromFTP(currentPath);
                            
                        }
                        catch
                        {
                            try
                            {
                                DeleteDirectoryFromFTP(currentPath);
                            }
                            catch
                            {
                                logger.WriteLine("Error.  Couldn't Delete: " + chPath.Action + " " + chPath.Path);
                        
                            }
                        }

                        
                        break;
                    default:
                        logger.WriteLine("Error.  Action not implemented: "+chPath.Action+" "+chPath.Path);
                        break;

                }

            }
            
            
        }

        private SvnError CreateInfoReceiver(IntPtr baton, SvnPath _path, SvnInfo info, AprPool pool)
        {
            switch(info.Kind)
            {
                case Svn.NodeKind.Dir:
                    CreateDirectoryOnFTP(currentPath);
                    break;

                case Svn.NodeKind.File:
                    GetFile(currentPath);
                    PushFileToFTP(currentPath);
                    File.Delete(tempFile);

                    break;

            }

            return SvnError.NoError;
        }


        private void CreateDirectoryOnFTP(SvnLogChangePathEntry chPath)
        {
            string uri = "ftp://" +
         ftpServerURL + ftpRemoteDir + chPath.Path;

            logger.WriteLine("FTP. Making Directory: " + uri);

            FtpWebRequest reqFTP;

            // Create FtpWebRequest object from the Uri provided
            reqFTP =
         (FtpWebRequest)FtpWebRequest.Create(new Uri(uri));

            // Provide the WebPermission Credintials
            reqFTP.Credentials = new NetworkCredential(ftpUserID, ftpPassword);

            // By default KeepAlive is true, where the control connection is not closed
            // after a command is executed.
            reqFTP.KeepAlive = false;

            reqFTP.Method = WebRequestMethods.Ftp.MakeDirectory;

            reqFTP.GetResponse();
        }

        private void DeleteFileFromFTP(SvnLogChangePathEntry chPath)
        {
            string uri = "ftp://" +
         ftpServerURL + ftpRemoteDir + chPath.Path;

            logger.WriteLine("FTP. Deleting: "+uri);

            FtpWebRequest reqFTP;

            // Create FtpWebRequest object from the Uri provided
            reqFTP =
         (FtpWebRequest)FtpWebRequest.Create(new Uri(uri));

            // Provide the WebPermission Credintials
            reqFTP.Credentials = new NetworkCredential(ftpUserID, ftpPassword);

            // By default KeepAlive is true, where the control connection is not closed
            // after a command is executed.
            reqFTP.KeepAlive = false;

            reqFTP.Method = WebRequestMethods.Ftp.DeleteFile;

            reqFTP.GetResponse();


        }

        private void DeleteDirectoryFromFTP(SvnLogChangePathEntry chPath)
        {
            string uri = "ftp://" +
         ftpServerURL + ftpRemoteDir + chPath.Path;

            logger.WriteLine("FTP. Deleting: " + uri);

            FtpWebRequest reqFTP;

            // Create FtpWebRequest object from the Uri provided
            reqFTP =
         (FtpWebRequest)FtpWebRequest.Create(new Uri(uri));

            // Provide the WebPermission Credintials
            reqFTP.Credentials = new NetworkCredential(ftpUserID, ftpPassword);

            // By default KeepAlive is true, where the control connection is not closed
            // after a command is executed.
            reqFTP.KeepAlive = false;

            reqFTP.Method = WebRequestMethods.Ftp.RemoveDirectory;

            reqFTP.GetResponse();


        }

        private void PushFileToFTP(SvnLogChangePathEntry chPath)
        {
            FileInfo fileInf = new FileInfo(tempFile);
            string uri = "ftp://" +
         ftpServerURL + ftpRemoteDir + chPath.Path;

            logger.WriteLine("FTP. Uploading File: " + uri);

            FtpWebRequest reqFTP;

            // Create FtpWebRequest object from the Uri provided
            reqFTP =
         (FtpWebRequest)FtpWebRequest.Create(new Uri(uri));

            // Provide the WebPermission Credintials
            reqFTP.Credentials = new NetworkCredential(ftpUserID, ftpPassword);

            // By default KeepAlive is true, where the control connection is not closed
            // after a command is executed.
            reqFTP.KeepAlive = false;

            // Specify the command to be executed.
            reqFTP.Method = WebRequestMethods.Ftp.UploadFile;

            // Specify the data transfer type.
            reqFTP.UseBinary = true;

            // Notify the server about the size of the uploaded file
            reqFTP.ContentLength = fileInf.Length;

            // The buffer size is set to 2kb
            int buffLength = 2048;
            byte[] buff = new byte[buffLength];
            int contentLen;

            // Opens a file stream (System.IO.FileStream) to read the file to be uploaded
            FileStream fs = fileInf.OpenRead();

            try
            {
                // Stream to which the file to be upload is written
                Stream strm = reqFTP.GetRequestStream();

                // Read from the file stream 2kb at a time
                contentLen = fs.Read(buff, 0, buffLength);

                // Till Stream content ends
                while (contentLen != 0)
                {
                    // Write Content from the file stream to the FTP Upload Stream
                    strm.Write(buff, 0, contentLen);
                    contentLen = fs.Read(buff, 0, buffLength);
                }

                // Close the file stream and the Request Stream
                strm.Close();
                fs.Close();
            }
            catch (Exception ex)
            {
                logger.WriteLine("Upload Error: "+ex.Message);
            }
        }

        private void GetFile(SvnLogChangePathEntry chPath)
        {
            AprFile.Flags flag = AprFile.Flags.Create | AprFile.Flags.Write | AprFile.Flags.Binary;
            AprFile aprFile = AprFile.Open(tempFile, flag, AprFile.Perms.OSDefault, client.Pool);
            
            SvnStream str = SvnStream.Create(aprFile, client.Pool);
            client.Cat2(str, path.Value + chPath.Path, rev, rev);
            aprFile.Close();
        }

        private void GetChanges()
        {
            
            client.Log2(new string[] { svnPath }, rev, rev, 0, true, false, LogMessageReceiver, IntPtr.Zero);

        }

        private SvnError LogMessageReceiver(IntPtr baton, AprHash changed_paths, int revision, AprString author,
                                            AprString date, AprString message, AprPool pool)
        {
            if (!changed_paths.IsNull)
            {
                foreach (AprHashEntry entry in changed_paths)
                {
                    string path = FromAprStringUTF8(new AprString(entry.Key));
                    SvnLogChangedPath chPath = new SvnLogChangedPath(entry.Value);
                    
                    logger.WriteLine(chPath.Action + " " + path);
                    
                    SvnLogChangePathEntry chEntry = new SvnLogChangePathEntry(path, chPath.Action,
                                            chPath.CopyFromPath.IsNull ? "" : FromAprStringUTF8(chPath.CopyFromPath),
                                            new SvnRevision(chPath.CopyFromRev));


                    logEntries.Add(chEntry);
                }
            }
            return SvnError.NoError;
        }

        public static string FromAprStringUTF8(AprString str)
        {
            byte[] bytes = new byte[str.Length];
            Marshal.Copy(str.ToIntPtr(), bytes, 0, bytes.Length);
            return System.Text.Encoding.UTF8.GetString(bytes);
        }

        
    }
}
